package filefunc

import (
	"errors"
	"fmt"
	"github.com/gogf/gf/errors/gerror"
	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/DO-module/new-filemanage-module/src/controller/errcode"
	"gitlab.local/DO-module/new-filemanage-module/src/core/sqlitedb/sqlitemodel"
	"gitlab.local/DO-module/new-filemanage-module/src/service/filesystem/filenavigation"
	"gitlab.local/TerraMaster/tos-modules/fileoperate"
	"gitlab.local/golibrary/utils"
	"golang.org/x/sys/unix"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

type Data struct {
	ChunkData  []byte `json:"chunk_data"`  //被压缩文件数据
	Size       int64  `json:"size"`        //数据块大小
	FileName   string `json:"file_name"`   //文件名称
	FileTotal  int64  `json:"file_total"`  //文件数量
	Path       string `json:"path"`        //文件路径
	Index      uint64 `json:"index"`       //当前索引
	TotalIndex uint64 `json:"total_index"` //总分片数
	BackUp     int8   `json:"back_up"`     //是否备份 0:否； 1:是
}

// FileUpload 文件上传
func (f *FileFunc) FileUpload(file filenavigation.HttpFile, dir string, totalIndex, index uint64, autoDelete bool, size int64, uname string, lastModTime int64) (string, error) {
	p, err := os.Stat(dir)
	if err != nil {
		return "", gerror.Wrap(err, "FileUpload")
	}
	if !p.IsDir() {
		return "", errors.New("the save path is not a folder")
	}

	//判断磁盘空间是否充足
	if index == 1 {
		var s sqlitemodel.Share
		f.db.DbGet().Table("share").Where("mntpath = ?", dir).Select("quota_flag").Find(&s)
		var pathFreeSize uint64
		//根据是否开启磁盘配额来查询目录的剩余空间
		if s.QuotaFlag == 1 {
			used, limit, b := utils.BtrfsShareUserQuota(dir)
			if b {
				return "", errors.New("Insufficient storage space")
			}
			free := limit - used
			pathFreeSize = uint64(free * 8)
		} else {
			pathFreeSize, err = utils.GetPathSize(dir)
			if err != nil {
				return "", errors.New("FileUpload1")
			}
		}

		if uint64(size) > pathFreeSize {
			return "", gerror.NewCode(errcode.InsufficientHardDiskCapacity)
		}
	}

	a := f.aclService.IsWrite(dir, uname)

	if !a {
		return "", gerror.NewCode(errcode.PermissionDenied)
	}

	if f.isXfsVolume(dir) {
		//xfs文件上传方式
		return f.FileUploadXfs(file, dir, totalIndex, index, uname, lastModTime)
	}
	//常规上传
	return f.FileUploadConvention(file, dir, totalIndex, index, autoDelete, size, uname, lastModTime)
}

// FileUploadMsg 文件上传(返回ws消息)
func (f *FileFunc) FileUploadMsg(
	file filenavigation.HttpFile,
	dir string,
	totalIndex,
	index, isMsg, fileIndex, totalFile uint64,
	autoDelete bool,
	size, totalSize, chunkSize int64,
	uname string,
	lastModTime int64) (string, error) {

	p, err := os.Stat(dir)
	if err != nil {
		return "", gerror.Wrap(err, "FileUpload")
	}
	if !p.IsDir() {
		return "", errors.New("the save path is not a folder")
	}

	//判断磁盘空间是否充足
	if index == 1 {
		var s sqlitemodel.Share
		f.db.DbGet().Table("share").Where("mntpath = ?", dir).Select("quota_flag").Find(&s)
		var pathFreeSize uint64
		//根据是否开启磁盘配额来查询目录的剩余空间
		if s.QuotaFlag == 1 {
			used, limit, b := utils.BtrfsShareUserQuota(dir)
			if b {
				return "", errors.New("Insufficient storage space")
			}
			free := limit - used
			pathFreeSize = uint64(free * 8)
		} else {
			pathFreeSize, err = utils.GetPathSize(dir)
			if err != nil {
				return "", errors.New("FileUpload1")
			}
		}

		if uint64(totalSize) > pathFreeSize {
			return "", gerror.NewCode(errcode.InsufficientHardDiskCapacity)
		}
	}

	a := f.aclService.IsWrite(dir, uname)

	if !a {
		return "", gerror.NewCode(errcode.PermissionDenied)
	}

	if f.isXfsVolume(dir) {
		//xfs文件上传方式
		return f.FileUploadXfs(file, dir, totalIndex, index, uname, lastModTime)
	}
	//常规上传
	return f.FileUploadConventionMsg(file, dir, totalIndex, index, isMsg, fileIndex, totalFile, autoDelete, size, totalSize, chunkSize, uname, lastModTime)
}

// isXfsVolume 判断路径是否为xfs卷
func (f *FileFunc) isXfsVolume(path string) bool {
	str := regexp.MustCompile(`^/Volume\d+($|/)`).FindString(path)
	if str == "" {
		return false
	}
	str = "/" + strings.Trim(str, "/")
	res, _ := utils.ShellExec(fmt.Sprintf(`df-json | grep '%s$' | grep xfs`, str))
	return strings.TrimSpace(res) != ""
}

// FileUploadXfs xfs文件上传方式
func (f *FileFunc) FileUploadXfs(file filenavigation.HttpFile, dir string, totalIndex, index uint64, uname string, lastModTime int64) (string, error) {
	if index == 1 {
		srcPath, err := utils.AutoFolderName(filepath.Join(dir, filepath.Base(file.Filename())))
		if err != nil {
			return "", gerror.Wrap(err, "FileUploadXfs")
		}

		_ = utils.SwitchUserRight()

		fi, err := os.OpenFile(srcPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
		if err != nil {
			return "", errors.New("FileUploadXfs1")
		}
		if f.xfsUpdate == nil {
			f.xfsUpdate = make(map[string]*os.File)
		}
		f.xfsUpdate[file.Filename()] = fi
		_ = unix.Flock(int(fi.Fd()), unix.LOCK_EX)
	}

	fi, b := f.xfsUpdate[file.Filename()]
	if !b {
		return "", errors.New("loss of previous data")
	}

	newFilePath := fi.Name()

	_ = f.reloadFileDelTimer(newFilePath, time.Minute, func() { _ = fi.Close() })
	src, err := file.Open()
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadXfs2")
	}

	_, err = io.Copy(fi, src)
	_ = src.Close()
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadXfs3")
	}

	if index < totalIndex {
		//上传未结束
		return newFilePath, nil
	}
	//上传结束
	_ = fi.Close()
	delete(f.xfsUpdate, file.Filename())

	_ = f.stopFileDelTimer(newFilePath)
	//修改文件拥有者
	//_ = f.Acl.Chown(newFilePath, uname)
	//修改文件最后修改时间
	_ = f.modifyLastModTime(newFilePath, lastModTime)
	//继承父级acl权限
	//_ = f.Acl.InheritedPermissions(newFilePath, "", false)

	return newFilePath, nil
}

// FileUploadConvention 常规文件上传方式
func (f *FileFunc) FileUploadConvention(file filenavigation.HttpFile, dir string, totalIndex, index uint64, autoDelete bool, size int64, uname string, lastModTime int64) (string, error) {
	//判断容量限制
	if index == 1 && f.fOptGetter.VolumeUserQuota(dir, uname, size) {
		return "", errors.New("drive space alert")
	}

	//临时文件前缀
	fileN := filepath.Base(file.Filename())
	//临时文件路径
	fileName := filepath.Join(dir, constant.IgnoreFilePre+fileN)
	src, err := file.Open()
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadConvention")
	}
	defer src.Close()

	if index > 1 && !utils.Exists(fileName) { //当索引值大于1时，判断临时文件是否存在，不存在为上传中断，上传失败
		return "", errors.New("file upload error, previous section of file missing")
	}

	if index == 1 { //重新开始，清除残留文件
		_ = os.Remove(fileName)
	}

	//_ = utils.SwitchUserRight(uname)

	fi, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadConvention1")
	}
	defer fi.Close()

	_ = unix.Flock(int(fi.Fd()), unix.LOCK_EX)

	if autoDelete {
		//文件删除计时器,一分钟后没请求则删除临时文件,有请求则重置计时器
		_ = f.reloadFileDelTimer(fileName, time.Minute, func() { _ = os.RemoveAll(fileName) })
	}

	if _, err = io.Copy(fi, src); err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention2")
	}

	//判断是否上传结束
	if index != totalIndex {
		return fileName, nil
	}

	if autoDelete {
		//上传成功停止计时器
		_ = f.stopFileDelTimer(fileName)
	}

	stat, err := os.Stat(fileName)
	if err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention3")
	}

	//判断文件大小是否相等
	if stat.Size() != size {
		_ = os.Remove(fileName)
		return "", errors.New("file sizes vary and files are missing")
	}

	newFileName, err := utils.AutoFolderName(filepath.Join(dir, fileN))
	if err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention4")
	}

	//重命名文件
	if err = os.Rename(fileName, newFileName); err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention5")
	}

	//修改文件拥有者
	//_ = f.Acl.Chown(newFileName, uname)
	//修改文件最后修改时间
	_ = f.modifyLastModTime(newFileName, lastModTime)
	//继承父级acl权限
	//_ = f.Acl.InheritedPermissions(newFileName, "", false)

	return newFileName, nil
}

// FileUploadConventionMsg 常规文件上传方式
func (f *FileFunc) FileUploadConventionMsg(file filenavigation.HttpFile,
	dir string,
	totalIndex, index, isMsg, fileIndex, totalFile uint64,
	autoDelete bool,
	size, totalSize, chunkSize int64,
	uname string,
	lastModTime int64) (string, error) {

	//判断容量限制
	if index == 1 && f.fOptGetter.VolumeUserQuota(dir, uname, size) {
		return "", errors.New("drive space alert")
	}

	//临时文件前缀
	fileN := filepath.Base(file.Filename())
	//临时文件路径
	fileName := filepath.Join(dir, constant.IgnoreFilePre+fileN)
	src, err := file.Open()
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadConvention")
	}
	defer src.Close()

	if index > 1 && !utils.Exists(fileName) { //当索引值大于1时，判断临时文件是否存在，不存在为上传中断，上传失败
		return "", errors.New("file upload error, previous section of file missing")
	}

	if index == 1 { //重新开始，清除残留文件
		_ = os.Remove(fileName)
	}

	if isMsg == 1 {
		if index == 1 && fileIndex == 1 {
			taskParams := fileoperate.TaskParams{}
			taskParams.Uname = uname
			taskParams.Size = totalSize
			taskParams.TType = fileoperate.Upload
			taskParams.ToPath = dir
			taskParams.FileTotal = uint8(totalFile)
			taskParams.Paths = []string{fileName}
			_, e := f.fOptGetter.RunFileOperationTask(taskParams)
			if e != nil {
				return "", gerror.Wrap(e, "Upload")
			}
		}
	}
	//_ = utils.SwitchUserRight(uname)

	fi, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadConvention1")
	}
	defer fi.Close()

	_ = unix.Flock(int(fi.Fd()), unix.LOCK_EX)

	if autoDelete {
		//文件删除计时器,一分钟后没请求则删除临时文件,有请求则重置计时器
		_ = f.reloadFileDelTimer(fileName, time.Minute, func() { _ = os.RemoveAll(fileName) })
	}

	if _, err = io.Copy(fi, src); err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention2")
	}
	if index <= totalIndex {
		paths := filepath.Join(dir, fileN)
		f.fOptGetter.SetBackgroundTaskLogsMsg(uname, totalSize, totalFile, paths, chunkSize)
	}
	//判断是否上传结束
	if index != totalIndex {
		return fileName, nil
	}

	if autoDelete {
		//上传成功停止计时器
		_ = f.stopFileDelTimer(fileName)
	}

	stat, err := os.Stat(fileName)
	if err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention3")
	}

	//判断文件大小是否相等
	if stat.Size() != size {
		_ = os.Remove(fileName)
		return "", errors.New("file sizes vary and files are missing")
	}

	newFileName, err := utils.AutoFolderName(filepath.Join(dir, fileN))
	if err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention4")
	}

	//重命名文件
	if err = os.Rename(fileName, newFileName); err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention5")
	}

	//修改文件拥有者
	_ = f.aclService.Chown(newFileName, uname)
	//修改文件最后修改时间
	_ = f.modifyLastModTime(newFileName, lastModTime)
	//继承父级acl权限
	_ = f.aclService.InheritedPermissions(newFileName, "", false)

	return newFileName, nil
}

// reloadFileDelTimer 延迟任务计时器,如果以存在计时器则重置，没有则添加
// path 路径
// t 延迟时间
// fun 触发逻辑
func (f *FileFunc) reloadFileDelTimer(path string, t time.Duration, fun func()) error {
	f.delRWMutex.RLock()
	d, b := f.delTimer[path]
	f.delRWMutex.RUnlock()
	if b {
		_ = d.t.Stop()
		_ = d.t.Reset(t)
	} else {
		f.delRWMutex.Lock()
		f.delTimer[path] = f.timer(path, t, fun)
		f.delRWMutex.Unlock()
	}
	return nil
}

func (f *FileFunc) timer(path string, t time.Duration, fun func()) *FileTimer {
	ft := &FileTimer{
		t:    time.NewTimer(t),
		stop: make(chan struct{}, 1),
	}
	go func() {
		select {
		case <-ft.t.C:
			fun()
		case <-ft.stop: //终止信号
		}
		f.delRWMutex.Lock()
		delete(f.delTimer, path)
		f.delRWMutex.Unlock()
	}()
	return ft
}

// stopFileDelTimer 终止延迟任务计时器
// path 路径
func (f *FileFunc) stopFileDelTimer(path string) error {
	f.delRWMutex.RLock()
	d, b := f.delTimer[path]
	f.delRWMutex.RUnlock()
	if b {
		_ = d.t.Stop()
		d.stop <- struct{}{}
	}
	return nil
}

// modifyLastModTime 修改文件最后修改时间(小于或等于0则不修改)
// lastModTime 最后修改时间(秒级时间戳)
func (f *FileFunc) modifyLastModTime(path string, lastModTime int64) error {
	if lastModTime <= 0 {
		return nil
	}
	t := time.Unix(lastModTime, 0)
	return os.Chtimes(path, t, t)
}
